sysroot: Add concept of deployment "pinning" 📌
authorColin Walters <walters@verbum.org>
Fri, 23 Feb 2018 17:46:32 +0000 (12:46 -0500)
committerAtomic Bot <atomic-devel@projectatomic.io>
Mon, 26 Feb 2018 19:06:59 +0000 (19:06 +0000)
Example user story: Jane rebases her OS to a new major version N, and wants to
keep around N-1 even after a few upgrades for a while so she can easily roll
back. I plan to add `rpm-ostree rebase --pin` to opt-in to this for example.

Builds on the new `libostree-transient` group to store pinning state there.

Closes: https://github.com/ostreedev/ostree/issues/1460
Closes: #1464
Approved by: jlebon

15 files changed:
Makefile-man.am
Makefile-ostree.am
apidoc/ostree-sections.txt
bash/ostree
man/ostree-admin-pin.xml [new file with mode: 0644]
src/libostree/libostree-devel.sym
src/libostree/ostree-deployment.c
src/libostree/ostree-deployment.h
src/libostree/ostree-sysroot.c
src/libostree/ostree-sysroot.h
src/ostree/ot-admin-builtin-pin.c [new file with mode: 0644]
src/ostree/ot-admin-builtin-status.c
src/ostree/ot-admin-builtins.h
src/ostree/ot-builtin-admin.c
tests/test-admin-deploy-2.sh

index e2f88a16e693c0cb3b33ada40369cccbf706d232..4d99cde1b292cb218a0bc741e1620c8fb80f72f5 100644 (file)
@@ -26,6 +26,7 @@ ostree-admin-config-diff.1 ostree-admin-deploy.1                      \
 ostree-admin-init-fs.1 ostree-admin-instutil.1 ostree-admin-os-init.1  \
 ostree-admin-status.1 ostree-admin-set-origin.1 ostree-admin-switch.1  \
 ostree-admin-undeploy.1 ostree-admin-upgrade.1 ostree-admin-unlock.1   \
+ostree-admin-pin.1 \
 ostree-admin.1 ostree-cat.1 ostree-checkout.1 ostree-checksum.1                \
 ostree-commit.1 ostree-export.1 ostree-gpg-sign.1 ostree-config.1      \
 ostree-diff.1 ostree-fsck.1 ostree-init.1 ostree-log.1 ostree-ls.1     \
index c366c84f9483234adf50f9300ed3f8e1eae0d5f9..cccbe3006862c7ff66af7ab53e4189776464c3c5 100644 (file)
@@ -74,6 +74,7 @@ ostree_SOURCES += \
        src/ostree/ot-admin-builtin-set-origin.c \
        src/ostree/ot-admin-builtin-status.c \
        src/ostree/ot-admin-builtin-switch.c \
+       src/ostree/ot-admin-builtin-pin.c \
        src/ostree/ot-admin-builtin-upgrade.c \
        src/ostree/ot-admin-builtin-unlock.c \
        src/ostree/ot-admin-builtins.h \
index 892aa0c08fb36cd8b3db2006392ee2d7128d346c..55f2e7a956f22f770d95da00ab836570bd0e2e63 100644 (file)
@@ -169,6 +169,7 @@ ostree_deployment_get_bootconfig
 ostree_deployment_get_origin
 ostree_deployment_get_origin_relpath
 ostree_deployment_get_unlocked
+ostree_deployment_is_pinned
 ostree_deployment_set_index
 ostree_deployment_set_bootserial
 ostree_deployment_set_bootconfig
@@ -509,6 +510,7 @@ ostree_sysroot_init_osname
 ostree_sysroot_deployment_set_kargs
 ostree_sysroot_deployment_set_mutable
 ostree_sysroot_deployment_unlock
+ostree_sysroot_deployment_set_pinned
 ostree_sysroot_write_deployments
 ostree_sysroot_write_deployments_with_options
 ostree_sysroot_write_origin_file
index fe8e3d2c0a1a7c87035288295fa58d6358d5b539..edef6cff87e4c743e8870923e112927b59d2f48d 100644 (file)
@@ -399,6 +399,37 @@ _ostree_admin_os_init() {
     return 0
 }
 
+_ostree_admin_pin() {
+    local boolean_options="
+        $main_boolean_options
+    "
+
+    local options_with_args="
+        --sysroot
+    "
+
+    local options_with_args_glob=$( __ostree_to_extglob "$options_with_args" )
+
+    case "$prev" in
+        --sysroot)
+            __ostree_compreply_dirs_only
+            return 0
+            ;;
+        $options_with_args_glob )
+            return 0
+            ;;
+    esac
+
+    case "$cur" in
+        -*)
+            local all_options="$boolean_options $options_with_args"
+            __ostree_compreply_all_options
+            ;;
+    esac
+
+    return 0
+}
+
 _ostree_admin_set_origin() {
     local boolean_options="
         $main_boolean_options
diff --git a/man/ostree-admin-pin.xml b/man/ostree-admin-pin.xml
new file mode 100644 (file)
index 0000000..db0787a
--- /dev/null
@@ -0,0 +1,82 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+    "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+Copyright 2018 Red Hat
+
+SPDX-License-Identifier: LGPL-2.0+
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the
+Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+-->
+
+<refentry id="ostree">
+
+    <refentryinfo>
+        <title>ostree admin pin</title>
+        <productname>OSTree</productname>
+
+        <authorgroup>
+            <author>
+                <contrib>Developer</contrib>
+                <firstname>Colin</firstname>
+                <surname>Walters</surname>
+                <email>walters@verbum.org</email>
+            </author>
+        </authorgroup>
+    </refentryinfo>
+
+    <refmeta>
+        <refentrytitle>ostree admin pin</refentrytitle>
+        <manvolnum>1</manvolnum>
+    </refmeta>
+
+    <refnamediv>
+        <refname>ostree-admin-pin</refname>
+        <refpurpose>Explicitly retain deployment at a given index</refpurpose>
+    </refnamediv>
+
+    <refsynopsisdiv>
+            <cmdsynopsis>
+                <command>ostree admin pin</command> <arg choice="req">INDEX</arg>
+            </cmdsynopsis>
+    </refsynopsisdiv>
+
+    <refsect1>
+        <title>Description</title>
+
+        <para>
+          Ensures the deployment at <literal>INDEX</literal>, will not be garbage
+          collected by default. This is termed "pinning".   If the
+          <literal>-u</literal> option is provided, undoes a pinning operation.
+        </para>
+    </refsect1>
+
+    <refsect1>
+        <title>Options</title>
+
+        <variablelist>
+            <varlistentry>
+                <term><option>--unpin</option>,<option>-u</option></term>
+
+                <listitem><para>
+                    Undoes a pinning operation.
+                </para></listitem>
+            </varlistentry>
+        </variablelist>
+    </refsect1>
+
+</refentry>
index 123627a4f9f5f6cc378176060aa36365f030c6f4..285ba5f5f3eff190bb24b3c132f6d08adc2cc464 100644 (file)
@@ -20,6 +20,8 @@
 /* Add new symbols here.  Release commits should copy this section into -released.sym. */
 LIBOSTREE_2018.3 {
   ostree_deployment_origin_remove_transient_state;
+  ostree_sysroot_deployment_set_pinned;
+  ostree_deployment_is_pinned;
 } LIBOSTREE_2018.2;
 
 /* Stub section for the stable release *after* this development one; don't
index 2c479fdb74732518bf373c6763286572839c4593..75a5bd1daed9eddb69b1b371682890c61433894a 100644 (file)
@@ -322,3 +322,20 @@ ostree_deployment_get_unlocked (OstreeDeployment *self)
 {
   return self->unlocked;
 }
+
+/**
+ * ostree_deployment_is_pinned:
+ * @self: Deployment
+ *
+ * See ostree_sysroot_deployment_set_pinned().
+ *
+ * Returns: `TRUE` if deployment will not be subject to GC
+ * Since: 2018.3
+ */
+gboolean
+ostree_deployment_is_pinned (OstreeDeployment *self)
+{
+  if (!self->origin)
+    return FALSE;
+  return g_key_file_get_boolean (self->origin, OSTREE_ORIGIN_TRANSIENT_GROUP, "pinned", NULL);
+}
index 985c813312540e9628568aed8626c532830999fe..612222a2aba5a5a0314e312bfdfcb907429e1701 100644 (file)
@@ -74,6 +74,9 @@ _OSTREE_PUBLIC
 GKeyFile *ostree_deployment_get_origin (OstreeDeployment *self);
 
 
+_OSTREE_PUBLIC
+gboolean ostree_deployment_is_pinned (OstreeDeployment *self);
+
 _OSTREE_PUBLIC
 void ostree_deployment_set_index (OstreeDeployment *self, int index);
 _OSTREE_PUBLIC
index 3479944472d5f9895a20cdaaa708d040d85c5999..2c12b78b334159271344dd463fe6e9728c3ae6ce 100644 (file)
@@ -1572,12 +1572,14 @@ ostree_sysroot_simple_write_deployment (OstreeSysroot      *sysroot,
 
       /* Retain deployment if:
        *   - we're explicitly asked to, or
+       *   - it's pinned
        *   - the deployment is for another osname, or
        *   - we're keeping pending deployments and this is a pending deployment, or
        *   - this is the merge or boot deployment, or
        *   - we're keeping rollback deployments and this is a rollback deployment
        */
       if (retain
+          || ostree_deployment_is_pinned (deployment)
           || !osname_matches
           || (retain_pending && !passed_crossover)
           || (is_booted || is_merge)
@@ -1832,3 +1834,45 @@ ostree_sysroot_deployment_unlock (OstreeSysroot     *self,
 
   return TRUE;
 }
+
+/**
+ * ostree_sysroot_deployment_set_pinned:
+ * @self: Sysroot
+ * @deployment: A deployment
+ * @is_pinned: Whether or not deployment will be automatically GC'd
+ * @error: Error
+ *
+ * By default, deployments may be subject to garbage collection. Typical uses of
+ * libostree only retain at most 2 deployments. If @is_pinned is `TRUE`, a
+ * metadata bit will be set causing libostree to avoid automatic GC of the
+ * deployment. However, this is really an "advisory" note; it's still possible
+ * for e.g. older versions of libostree unaware of pinning to GC the deployment.
+ *
+ * This function does nothing and returns successfully if the deployment
+ * is already in the desired pinning state.
+ *
+ * Since: 2018.3
+ */
+gboolean
+ostree_sysroot_deployment_set_pinned (OstreeSysroot     *self,
+                                      OstreeDeployment  *deployment,
+                                      gboolean           is_pinned,
+                                      GError           **error)
+{
+  const gboolean current_pin = ostree_deployment_is_pinned (deployment);
+  if (is_pinned == current_pin)
+    return TRUE;
+
+  g_autoptr(OstreeDeployment) deployment_clone = ostree_deployment_clone (deployment);
+  GKeyFile *origin_clone = ostree_deployment_get_origin (deployment_clone);
+
+  if (is_pinned)
+    g_key_file_set_boolean (origin_clone, OSTREE_ORIGIN_TRANSIENT_GROUP, "pinned", TRUE);
+  else
+    g_key_file_remove_key (origin_clone, OSTREE_ORIGIN_TRANSIENT_GROUP, "pinned", NULL);
+
+  if (!ostree_sysroot_write_origin_file (self, deployment, origin_clone, NULL, error))
+    return FALSE;
+
+  return TRUE;
+}
index 830ed272d3104ebc243dbec5fcbaba5139c62e73..e4763d3755d2af8eb922e50fe287ad609a14c12f 100644 (file)
@@ -181,6 +181,12 @@ gboolean ostree_sysroot_deployment_set_mutable (OstreeSysroot     *self,
                                                 GCancellable      *cancellable,
                                                 GError           **error);
 
+_OSTREE_PUBLIC
+gboolean ostree_sysroot_deployment_set_pinned (OstreeSysroot     *self,
+                                               OstreeDeployment  *deployment,
+                                               gboolean           is_pinned,
+                                               GError           **error);
+
 _OSTREE_PUBLIC
 gboolean ostree_sysroot_deployment_unlock (OstreeSysroot     *self,
                                            OstreeDeployment  *deployment,
diff --git a/src/ostree/ot-admin-builtin-pin.c b/src/ostree/ot-admin-builtin-pin.c
new file mode 100644 (file)
index 0000000..e26ea92
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2018 Colin Walters <walters@verbum.org>
+ *
+ * SPDX-License-Identifier: LGPL-2.0+
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include "ot-main.h"
+#include "ot-admin-builtins.h"
+#include "ot-admin-functions.h"
+#include "ostree.h"
+#include "otutil.h"
+
+/* ATTENTION:
+ * Please remember to update the bash-completion script (bash/ostree) and
+ * man page (man/ostree-admin-pin.xml) when changing the option list.
+ */
+
+static gboolean opt_unpin;
+
+static GOptionEntry options[] = {
+  { "unpin", 'u', 0, G_OPTION_ARG_NONE, &opt_unpin, "Unset pin", NULL },
+  { NULL }
+};
+
+gboolean
+ot_admin_builtin_pin (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error)
+{
+  g_autoptr(GOptionContext) context = g_option_context_new ("INDEX");
+  g_autoptr(OstreeSysroot) sysroot = NULL;
+  if (!ostree_admin_option_context_parse (context, options, &argc, &argv,
+                                          OSTREE_ADMIN_BUILTIN_FLAG_SUPERUSER,
+                                          invocation, &sysroot, cancellable, error))
+    return FALSE;
+
+  if (argc < 2)
+    {
+      ot_util_usage_error (context, "INDEX must be specified", error);
+      return FALSE;
+    }
+
+  const char *deploy_index_str = argv[1];
+  const int deploy_index = atoi (deploy_index_str);
+
+  g_autoptr(OstreeDeployment) target_deployment = ot_admin_get_indexed_deployment (sysroot, deploy_index, error);
+  if (!target_deployment)
+    return FALSE;
+
+
+  gboolean current_pin = ostree_deployment_is_pinned (target_deployment);
+  const gboolean desired_pin = !opt_unpin;
+  if (current_pin == desired_pin)
+    g_print ("Deployment is already %s\n", current_pin ? "pinned" : "unpinned");
+  else
+    {
+      if (!ostree_sysroot_deployment_set_pinned (sysroot, target_deployment, desired_pin, error))
+        return FALSE;
+      g_print ("Deployment is now %s\n", desired_pin ? "pinned" : "unpinned");
+    }
+
+  return TRUE;
+}
index 0279c5aff2927fda5114d89bdee6119982f03c8f..02991309441bafd2827b377306f8dbf8c5fa2aa5 100644 (file)
@@ -149,6 +149,8 @@ ot_admin_builtin_status (int argc, char **argv, OstreeCommandInvocation *invocat
                        ostree_deployment_unlocked_state_to_string (unlocked),
                        red_bold_suffix);
             }
+          if (ostree_deployment_is_pinned (deployment))
+            g_print ("    Pinned: yes\n");
           if (!origin)
             g_print ("    origin: none\n");
           else
index 399962439399450eb4c5614fef054eae105e0fe7..a81f4d62d41680973f4993ed0640efa9840d73e7 100644 (file)
@@ -39,6 +39,7 @@ BUILTINPROTO(init_fs);
 BUILTINPROTO(undeploy);
 BUILTINPROTO(deploy);
 BUILTINPROTO(cleanup);
+BUILTINPROTO(pin);
 BUILTINPROTO(unlock);
 BUILTINPROTO(status);
 BUILTINPROTO(set_origin);
index f4e687e9827b8914397aedf581b6238ac0ba0ee6..fd6d9a8f92d9fecc60f0af75a1bbea4c37dc1773 100644 (file)
@@ -54,6 +54,9 @@ static OstreeCommand admin_subcommands[] = {
   { "set-origin", OSTREE_BUILTIN_FLAG_NO_REPO,
     ot_admin_builtin_set_origin,
     "Set Origin and create a new origin file" },
+  { "pin", OSTREE_BUILTIN_FLAG_NO_REPO,
+    ot_admin_builtin_pin,
+    "Change the \"pinning\" state of a deployment" },
   { "status", OSTREE_BUILTIN_FLAG_NO_REPO,
     ot_admin_builtin_status,
     "List deployments" },
index 4ecaf67ad9052575005bdb25369974b6ff79324c..eab0a3d3db4e330074398bae43aa6c33a2ebcd03 100755 (executable)
@@ -26,7 +26,7 @@ set -euo pipefail
 # Exports OSTREE_SYSROOT so --sysroot not needed.
 setup_os_repository "archive" "syslinux"
 
-echo "1..3"
+echo "1..6"
 
 ${CMD_PREFIX} ostree --repo=sysroot/ostree/repo pull-local --remote=testos testos-repo testos/buildmaster/x86_64-runtime
 rev=$(${CMD_PREFIX} ostree --repo=sysroot/ostree/repo rev-parse testos/buildmaster/x86_64-runtime)
@@ -63,3 +63,50 @@ assert_has_dir sysroot/boot/ostree/testos-${bootcsum}
 assert_file_has_content sysroot/ostree/deploy/testos/deploy/${newrev}.0/etc/os-release 'NAME=TestOS'
 
 echo "ok manual cleanup"
+
+assert_n_pinned() {
+    local n=$1
+    ${CMD_PREFIX} ostree admin status > status.txt
+    local n_pinned="$(grep -F -c -e 'Pinned: yes' < status.txt)"
+    if test "${n_pinned}" '!=' "${n}"; then
+        cat status.txt
+        fatal "${n_pinned} != ${n}"
+    fi
+}
+assert_n_deployments() {
+    local n=$1
+    ${CMD_PREFIX} ostree admin status > status.txt
+    local n_deployments="$(grep -F -c -e 'Version: ' < status.txt)"
+    if test "${n_deployments}" '!=' "${n}"; then
+        cat status.txt
+        fatal "${n_deployments} != ${n}"
+    fi
+}
+assert_n_pinned 0
+${CMD_PREFIX} ostree admin pin 0
+assert_n_pinned 1
+${CMD_PREFIX} ostree admin pin -u 0
+assert_n_pinned 0
+echo "ok pin unpin"
+
+${CMD_PREFIX} ostree admin pin 0
+${CMD_PREFIX} ostree admin pin 1
+assert_n_pinned 2
+assert_n_deployments 2
+os_repository_new_commit
+${CMD_PREFIX} ostree admin upgrade --os=testos
+assert_n_pinned 2
+assert_n_deployments 3
+echo "ok pin across upgrades"
+
+${CMD_PREFIX} ostree admin pin -u 1
+os_repository_new_commit
+${CMD_PREFIX} ostree admin upgrade --os=testos
+assert_n_pinned 1
+assert_n_deployments 3
+os_repository_new_commit
+${CMD_PREFIX} ostree admin upgrade --os=testos
+assert_n_pinned 1
+assert_n_deployments 3
+
+echo "ok pinning"